﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Newtonsoft.Json.Linq;

namespace JsonEditor
{
    public class EditedJToken : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public EditedJToken(JToken jToken)
        {
            this.JToken = jToken;

            if (this.IsProperty)
            {
                this.Name = this.JProperty.Name;
                this.Value = this.JProperty.Value.ToString();
                this.IsNull = (this.JProperty.Value.Type == JTokenType.Null);
                this.Type = this.GetValueType(this.JProperty.Value.Type);
            }
            else if (this.IsValue)
            {
                if (this.JValue.Value != null)
                    this.Value = this.JValue.Value.ToString();
                this.IsNull = (this.JValue.Type == JTokenType.Null);
                this.Type = this.GetValueType(this.JValue.Type);
            }
        }

        #region "       Properties      "

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                if (_name != value)
                {
                    _name = value;
                    OnPropertyChanged("Name");
                }
            }
        }

        private string _value;
        public string Value
        {
            get { return _value; }
            set
            {
                if (_value != value)
                {
                    _value = value;
                    OnPropertyChanged("Value");
                }
            }
        }

        private bool _isNull;
        public bool IsNull
        {
            get { return _isNull; }
            set
            {
                if (_isNull != value)
                {
                    _isNull = value;
                    OnPropertyChanged("IsNull");
                }
            }
        }

        private ValueType _type;
        public ValueType Type
        {
            get { return _type; }
            set
            {
                if (_type != value)
                {
                    _type = value;

                    OnPropertyChanged("Type");
                    OnPropertyChanged("CanEditValue");
                }
            }
        }

        public JToken JToken { get; private set; }

        public JProperty JProperty
        {
            get
            {
                return this.JToken as JProperty;
            }
        }

        public JValue JValue
        {
            get
            {
                return this.JToken as JValue;
            }
        }

        public bool IsProperty
        {
            get
            {
                return JProperty != null;
            }
        }

        public bool IsValue
        {
            get
            {
                return JValue != null;
            }
        }

        public bool IsNameChanged
        {
            get
            {
                return JProperty != null && JProperty.Name != Name;
            }
        }

        public bool CanEditValue
        {
            get
            {
                return (this.Type != ValueType.Object && this.Type != ValueType.Array);
            }
        }

        public IEnumerable<ValueType> ValueTypes
        {
            get
            {
                if (this.IsValue)
                    return new List<ValueType>() { ValueType.Boolean, ValueType.Number, ValueType.String };
                else
                    return new List<ValueType>() { ValueType.Boolean, ValueType.Number, ValueType.String, ValueType.Array, ValueType.Object };
            }
        }

        #endregion

        #region "       Methods     "

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private ValueType GetValueType(JTokenType type)
        {
            if (type == JTokenType.Integer || type == JTokenType.Float)
                return ValueType.Number;
            else if (type == JTokenType.Boolean)
                return ValueType.Boolean;
            else if (type == JTokenType.Object)
                return ValueType.Object;
            else if (type == JTokenType.Array)
                return ValueType.Array;
            else
                return ValueType.String;
        }

        public JToken CreateNewToken()
        {
            if (this.IsProperty)
                return new JProperty(this.Name, this.ParseToValue(this.Type, this.Value, this.IsNull));
            else if (this.IsValue)
                return new JValue(this.ParseToValue(this.Type, this.Value, this.IsNull));
            else
                return null;
        }

        private object ParseToValue(ValueType type, string text, bool isNull)
        {
            if (isNull)
                return null;
            else
            {
                switch (type)
                {
                    case ValueType.String:
                        return text;
                    case ValueType.Number:
                        return double.Parse(text);
                    case ValueType.Boolean:
                        return bool.Parse(text);
                    case ValueType.Object:
                        if (this.IsProperty)
                        {
                            if (this.JProperty.Value.Type == JTokenType.Object)
                                return this.JProperty.Value.DeepClone();
                            else
                                return new JObject();
                        }
                        else
                            return null;
                    case ValueType.Array:
                        if (this.IsProperty)
                        {
                            if (this.JProperty.Value.Type == JTokenType.Array)
                                return this.JProperty.Value.DeepClone();
                            else
                                return new JArray();
                        }
                        else
                            return null;
                    default:
                        return null;
                }
            }
        }

        public bool IsValid()
        {
            bool boolValue = false;
            double doubleValue = 0;
            bool isValidName = false;
            bool isValidValue = false;

            if (this.IsProperty)
                isValidName = !string.IsNullOrEmpty(this.Name) && ((this.IsNameChanged) ? !this.JProperty.Parent.IsExistingProperty(this.Name) : true);

            switch (this.Type)
            {
                case ValueType.String:
                case ValueType.Object:
                case ValueType.Array:
                    isValidValue = true;
                    break;
                case ValueType.Number:
                    isValidValue = double.TryParse(this.Value, out doubleValue);
                    break;
                case ValueType.Boolean:
                    isValidValue = bool.TryParse(this.Value, out boolValue);
                    break;
            }

            if (this.IsNull)
                isValidValue = true;

            if (this.IsProperty)
                return isValidName && isValidValue;
            else if (this.IsValue)
                return isValidValue;
            else
                return false;
        }

        #endregion
    }

}
